﻿#region copyright
//<copyright>
// Copyright(C) 2012 TrackerRealm Corporation
// This file is part of the open source project - Jazz. http://jazz.codeplex.com
// 
// Jazz is open software: you can redistribute it and/or modify it 
// under the terms of the GNU Affero General Public License (AGPL) as published by 
// the Free Software Foundation, version 3 of the License.
// 
// Jazz is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty 
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
//  See the GNU Affero General Public License (AGPL) for more details.
// 
// You should have received a copy of the GNU General Public 
// License along with Jazz.  If not, see <http://www.gnu.org/licenses/>.
//
// REMOVAL OF THIS NOTICE IS VIOLATION OF THE COPYRIGHT. 
//</copyright>
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using TrackerRealm.Jazz.Common;

namespace TrackerRealm.Jazz.Client
{
    /// <summary>
    /// A '<see cref="jClass"/>' instance provides class information, or static information, for a
    /// a '<see cref="jObject"/>' type (JazzObjectType).
    /// </summary>
    public class jClass : IjStorageUnit, INotifyPropertyChanged 
    {
        /// <summary>
        /// 
        /// </summary>
        public static jClass Placeholder = new jClass(typeof(jObject), null);
        #region static constructor
        /// <summary>
        /// Static Constructor
        /// </summary>
        static jClass()
        {
            jClass.jObject_WorkspaceField = GetField(typeof(jObject), "workspace");
            jClass.jObject_ClientNexusField = GetField(typeof(jObject), "clientNexus");
            jClass.jObject_JazzClassField = GetField(typeof(jObject), "jazzClass");
            jClass.jObject_IdField = GetField(typeof(jObject), "id");
        }
        #endregion
        #region Constructor
        /// <summary>
        /// 
        /// </summary>
        internal jClass()
        {
        }
        /// <summary>
        /// Constructor
        /// </summary>
        internal jClass(Type jazzObjectType, ClientNexusBase nexus)
        {
            this.ClientNexus = nexus;
            if (!(jazzObjectType.IsSubclassOf(typeof(jObject)) || 
                jazzObjectType == typeof(jObject))// so Empty can be created - should be removed
                )
                throw new ArgumentException(string.Format("The class '{0}' needs to inherit from 'jObject'.", jazzObjectType.FullName));
            this.JazzObjectType = jazzObjectType;
            //this.EmptyInstance = empty;
            this.guid = Guid.NewGuid();
            this.IsJazzObjectNameUnique = UniqueNameAttribute.IsNameUniqueValue(this.JazzObjectType);
        }
        #endregion
        #region Static Properties
        /// <summary>
        /// Convenience property.  Returns the 'FieldInfo' for the the "clientNexus' field in the class 'jObject'.
        /// </summary>
        internal static FieldInfo jObject_ClientNexusField { get; private set; }
        /// <summary>
        /// Convenience property.  Returns the 'FieldInfo' for the the "jazzClass' field in the class 'jObject'.
        /// </summary>
        internal static FieldInfo jObject_JazzClassField { get; private set; }
        /// <summary>
        /// Convenience property.  Returns the 'FieldInfo' for the the "workspace' field in the class 'jObject'.
        /// </summary>
        internal static FieldInfo jObject_WorkspaceField { get; private set; }
        /// <summary>
        /// Convenience property.  Returns the 'FieldInfo' for the the "id' field in the class 'jObject'.
        /// </summary>
        internal static FieldInfo jObject_IdField { get; private set; }
        #endregion
        #region CrossCheck
        /// <summary>
        /// When a Jazz object type (any class that inherits from jObject) is bound to the nexus,
        /// it is crossed checked against a number of common configuration errors.
        /// </summary>
        public void CrossCheck(ClientNexusBase nexus)
        {
            #region Create Via Dictionary - used in 'Check StateTo Attribute'
            Dictionary<string, MemberInfo> viaDict = new Dictionary<string, MemberInfo>();
            foreach (MemberInfo member in this.JazzObjectType.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
            {
                if (member.MemberType != MemberTypes.Property && member.MemberType != MemberTypes.Method) continue;
                if (viaDict.ContainsKey(member.Name)) continue; //base property - has been overridden - overridden property in cache.
                viaDict.Add(member.Name, member);
            }
            #endregion
            #region Check for Jazz Attribute
#if DEBUG
            {
                Type t = this.JazzObjectType;
                while (t.Name != "jObject") 
                {
                    foreach (MemberInfo member in t.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
                    {
                        if (member.Name == ".cctor") continue;
                        if (member.MemberType == MemberTypes.Constructor && !(member.DeclaringType == typeof(jObject) || member.DeclaringType == typeof(object)))
                        {
                            object[] jArray = member.GetCustomAttributes(typeof(JazzAttribute), true);
                            if (jArray.Length == 0)
                            {
                                throw new JazzConfigurationException(
                                    ConfigurationErrorType.JazzAttributeMissing,
                                    string.Format(
                                    "The Jazz class '{0}' needs to specify the 'Jazz' attribute.",
                                    member.DeclaringType.FullName));
                            }
                        }
                    }
                    t = t.BaseType;
                }
                
            }
#endif
            #endregion
            foreach (MemberInfo member in this.JazzObjectType.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
            {
                #region Insure 'CreateEmpty' is static
                if (member.MemberType == MemberTypes.Method && member.Name == "CreateEmpty")
                {
                    if (!((MethodInfo)member).IsStatic)
                    {
                        throw new JazzConfigurationException(
                            ConfigurationErrorType.CreateEmptyMethodNeedsToBeStatic,
                            string.Format(
                                    "The method 'CreateEmpty' ('{0}') needs to be static.  Add the 'static' keyword the method declaration.",
                                    member.DeclaringType.FullName));
                    }
                }
                #endregion
                #region Check GrantRoles Attribute
                object[] roleArray = member.GetCustomAttributes(typeof(GrantRolesAttribute), true);
                {
                    if (roleArray.Length > 0)
                    {
                        if (member.MemberType == MemberTypes.Method)
                        {
                            #region Attribute can not be used on a static method
                            if (((MethodInfo)member).IsStatic)
                            {
                                throw new JazzConfigurationException(ConfigurationErrorType.GrantMemberStatic,
                                    string.Format(
                                    "The attribute 'GrantRoles' is not allowed on a static member.  " +
                                    "Remove the attribute from the method '{0}{1}').",
                                            member.Name,
                                            member.DeclaringType.FullName));
                            }
                            #endregion
                            #region Attribute must be used on public method
                            if (!((MethodInfo)member).IsPublic)
                            {
                                throw new JazzConfigurationException(
                                    ConfigurationErrorType.GrantMemberNotPublic,
                                    string.Format(
                                    "The attribute 'GrantRoles' is not allowed on non public method.  " +
                                    "Remove the attribute or make the method public.  Method '{0}':'{1}').",
                                            member.Name,
                                            member.DeclaringType.FullName));
                            }
                            #endregion
                        }
                        if (member.MemberType == MemberTypes.Property)
                        {
                            #region Attribute can not be used on a static property
                            PropertyInfo propInfo = (PropertyInfo)member;
                            if ((propInfo.CanRead && propInfo.GetGetMethod(true).IsStatic) ||
                                (propInfo.CanWrite && propInfo.GetSetMethod(true).IsStatic))
                            {
                                throw new JazzConfigurationException(ConfigurationErrorType.GrantMemberStatic,
                                    string.Format(
                                    "The attribute 'GrantRoles' is not allowed on a static member.  " +
                                    "Remove the attribute from the method '{0}{1}').",
                                            member.Name,
                                            member.DeclaringType.FullName));
                            }
                            #endregion
                            #region Attribute must be used on a Public property
                            if (propInfo.GetSetMethod() == null && propInfo.GetGetMethod() == null)
                            {
                                throw new JazzConfigurationException(
                                    ConfigurationErrorType.GrantMemberNotPublic,
                                    string.Format(
                                    "The attribute 'GrantRoles' is not allowed on non public property.  " +
                                    "Remove the attribute or make the property public.  Property '{0}':'{1}').",
                                            member.Name,
                                            member.DeclaringType.FullName));
                            }
                            #endregion
                        }
                        #region check the role names are valid
                        GrantRolesAttribute gra = (GrantRolesAttribute)roleArray[0];
                        foreach (string role in gra.Roles)
                        {
                            if (!nexus.Cache.NamedObjects.Contains<jRoleBase>(role) && nexus.RoleCheckCollection.FirstOrDefault(r => r == role) == null)
                                throw new ApplicationException(string.Format(
                                    "The role '{0}' specifed in the 'GrantRole' attribute " +
                                    "on member '{1}-{2}' does not exist.  To correct this problem 1)Check for a spelling error or 2)create a Jazz role object (jRole) or " +
                                    "3) add the role name to 'NexusConfig' object, 'RoleCheckCollection'.",
                                    role,
                                    member.Name,
                                    member.DeclaringType.FullName));
                        }
                        #endregion
                    }
                }
                #endregion
                #region Check GrantStates Attribute
                object[] sArray = member.GetCustomAttributes(typeof(GrantStatesAttribute), true);
                {
                    if (sArray.Length > 0)
                    {
                        if (member.MemberType == MemberTypes.Method)
                        {
                            if (((MethodInfo)member).IsStatic)
                            {
                                throw new JazzConfigurationException(ConfigurationErrorType.GrantMemberStatic,
                                    string.Format(
                                    "The attribute 'GrantStates' is not allowed on a static member.  " +
                                    "Remove the attribute from the method '{0}{1}').",
                                            member.Name,
                                            member.DeclaringType.FullName));
                            }

                            if (!((MethodInfo)member).IsPublic)
                            {
                                throw new JazzConfigurationException(
                                    ConfigurationErrorType.GrantMemberNotPublic,
                                    string.Format(
                                    "The attribute 'GrantStates' is not allowed on non public method.  " +
                                    "Remove the attribute or make the method public.  Method '{0}':'{1}').",
                                            member.Name,
                                            member.DeclaringType.FullName));
                            }
                        }
                        if (member.MemberType == MemberTypes.Property)
                        {
                            PropertyInfo propInfo = (PropertyInfo)member;
                            if ((propInfo.CanRead && propInfo.GetGetMethod(true).IsStatic) ||
                                (propInfo.CanWrite && propInfo.GetSetMethod(true).IsStatic))
                            {
                                
                                throw new JazzConfigurationException(ConfigurationErrorType.GrantMemberStatic,
                                    string.Format(
                                    "The attribute 'GrantStates' is not allowed on a static member.  " +
                                    "Remove the attribute from the method '{0}{1}').",
                                            member.Name,
                                            member.DeclaringType.FullName));
                            }
                            if (propInfo.GetSetMethod() == null && propInfo.GetGetMethod() == null)
                            {
                                throw new JazzConfigurationException(
                                    ConfigurationErrorType.GrantMemberNotPublic,
                                    string.Format(
                                    "The attribute 'GrantStates' is not allowed on non public property.  " +
                                    "Remove the attribute or make the property public.  Property '{0}':'{1}').",
                                            member.Name,
                                            member.DeclaringType.FullName));
                            }
                            GrantStatesAttribute gsa = (GrantStatesAttribute)sArray[0];
                            foreach (string state in gsa.States)
                            {
                                if (!this.States.Contains(state))
                                    throw new ApplicationException(string.Format(
                                        "The state '{0}' specifed in the 'GrantStates' attribute " +
                                        "on member '{1}':'{2}' does not exist.  Check for a spelling error and verify that the state has been defined.",
                                        state,
                                        member.Name,
                                        member.DeclaringType.FullName));
                            }
                        }
                    }
                }
                #endregion

                #region Check GrantProperty Attribute
                object[] pArray = member.GetCustomAttributes(typeof(GrantPropertyAttribute), true);
                {
                    if (pArray.Length > 0)
                    {
                        if (member.MemberType == MemberTypes.Method)
                        {
                            if (((MethodInfo)member).IsStatic)
                            {

                                throw new JazzConfigurationException(ConfigurationErrorType.GrantMemberStatic,
                                    string.Format(
                                    "The attribute 'GrantProperty' is not allowed on a static member.  " +
                                    "Remove the attribute from the method '{0}{1}').",
                                            member.Name,
                                            member.DeclaringType.FullName));
                            }
                            if (!((MethodInfo)member).IsPublic)
                            {
                                throw new JazzConfigurationException(
                                    ConfigurationErrorType.GrantMemberNotPublic,
                                    string.Format(
                                    "The attribute 'GrantProperty' is not allowed on non public method.  " +
                                    "Remove the attribute or make the method public.  Method '{0}':'{1}').",
                                            member.Name,
                                            member.DeclaringType.FullName));
                            }
                        }
                        if (member.MemberType == MemberTypes.Property)
                        {
                            PropertyInfo propInfo = (PropertyInfo)member;
                            if ((propInfo.CanRead && propInfo.GetGetMethod().IsStatic) ||
                                (propInfo.CanWrite && propInfo.GetSetMethod().IsStatic))
                            {
                                throw new JazzConfigurationException(
                                    ConfigurationErrorType.GrantMemberNotPublic,
                                    string.Format(
                                    "The attribute 'GrantProperty' is not allowed on non public property.  " +
                                    "Remove the attribute or make the property public.  Property '{0}':'{1}').",
                                            member.Name,
                                            member.DeclaringType.FullName));
                            }

                            if (propInfo.GetSetMethod() == null && propInfo.GetGetMethod() == null)
                            {
                                throw new JazzConfigurationException(
                                    ConfigurationErrorType.GrantMemberNotPublic,
                                    string.Format(
                                    "The attribute 'GrantProperty' is not allowed on non public property.  " +
                                    "Remove the attribute or make the property public.  Property '{0}':'{1}').",
                                            member.Name,
                                            member.DeclaringType.FullName));
                            }
                        }
                        GrantPropertyAttribute gpa = (GrantPropertyAttribute)pArray[0];
                        MethodInfo m;
                        if (!this.TryGetMethodInfo("get_" + gpa.Property, out m))
                        {
                            throw new ApplicationException(string.Format(
                                    "The attribute 'GrantProperty' specifies a property name '{0}':'{1}' that either does not exist or does not have a get accessor.  ",
                                            gpa.Property,
                                            member.DeclaringType.FullName));
                        }
                        if (m.IsStatic)
                        {
                            throw new ApplicationException(string.Format(
                                    "The attribute 'GrantProperty' specifies a property name '{0}':'{1}' that is static.   This property must be an instance property.  ",
                                            gpa.Property,
                                            member.DeclaringType.FullName));
                        }
                    }
                }
                #endregion
                #region Check Duplicate Grant Attributes On Property and Accessors
                {
                    if ((sArray.Length != 0 || pArray.Length != 0 && roleArray.Length != 0) &&
                        (member is MethodInfo) &&
                        (member.Name.StartsWith("get_") || member.Name.StartsWith("set_")))
                    {
                        PropertyInfo p = ((MethodInfo)member).GetAccessorProperty();
                        sArray = p.GetCustomAttributes(typeof(GrantStatesAttribute), true);
                        roleArray = p.GetCustomAttributes(typeof(GrantRolesAttribute), true);
                        pArray = p.GetCustomAttributes(typeof(GrantPropertyAttribute), true);
                        if (sArray.Length != 0 || pArray.Length != 0 || roleArray.Length != 0)
                            throw new JazzConfigurationException(
                                ConfigurationErrorType.GrantAttributesOnBothPropertyAndAccessors,
                                string.Format(
                                    "The property '{0}':'{1}' has 'Grant' attributes on the property and 1 or more of the accessors.  " +
                                    "'Grant' attributes may be placed only on the property or one or more of its accessors.",
                                            p.Name,
                                            p.DeclaringType.FullName));
                    }
                }
                #endregion
                #region Check StatesTo Attribute
                {
                    object[] stArray = member.GetCustomAttributes(typeof(StateToAttribute), true);
                    if (stArray.Length > 0)
                    {

                        StateToAttribute sta = (StateToAttribute)stArray[0];
                        if (!this.States.Contains(sta.ToState))
                            throw new ApplicationException(string.Format(
                                "The state '{0}' specifed in the 'StateTo' attribute " +
                                "on member '{1}-{2}' does not exist.  Check for a spelling error and verify that the state has been defined.",
                                sta.ToState,
                                member.Name,
                                member.DeclaringType.FullName));
                        if (!viaDict.ContainsKey(sta.Via))
                            throw new JazzConfigurationException(ConfigurationErrorType.StateToViaMethodPropertyDoesNotExist,
                                string.Format(
                                "The 'Via' method or property '{0}' specifed in the 'StateTo' attribute " +
                                "on member '{1}'-'{2}' does not exist.  Check for a spelling error and verify that the instance property or method has been defined.",
                                sta.Via,
                                member.Name,
                                member.DeclaringType.FullName));

                    }
                }
                #endregion
                #region Check UI Attribute
                {
                    object[] uiArray = member.GetCustomAttributes(typeof(UIAttribute), true);
                    if (uiArray.Length > 0)
                    {

                        UIAttribute uia = (UIAttribute)uiArray[0];
                        if (uia.TagName != "")
                        {
                            object tag = jClass.GetTag(member, uia.TagName, null);
                            if (tag == null)
                                throw new ApplicationException(string.Format(
                                    "The tag '{0}' specifed in the 'UI' attribute " +
                                    "on member '{1}-{2}' does not exist.  Check for a spelling error and verify that the static Tag property or field has been defined.",
                                    uia.TagName,
                                    member.Name,
                                    member.DeclaringType.FullName));
                        }
                    }
                }
                #endregion
                #region Check State Attribute
                {
                    object[] attArray = member.GetCustomAttributes(true);
                    object[] stateAttrArray = attArray.Where(a => a.GetType().Name == "StateAttribute").ToArray();
                    object[] stateStartArray = attArray.Where(a => a.GetType().Name == "StartStateAttribute").ToArray();
                    object[] finalStartArray = attArray.Where(a => a.GetType().Name == "FinalStateAttribute").ToArray();
                    if (stateAttrArray.Length > 0 || stateStartArray.Length > 0 || finalStartArray.Length > 0)
                    {
                        MethodInfo state = (MethodInfo)member;
                        if (state.IsStatic)
                            throw new JazzConfigurationException(ConfigurationErrorType.StateAttributeMustbeUsedOnInstanceMethod,
                                string.Format(
                                "The state attribute must be used on instance method.  The attribute was used on a member '{0}':'{1}'",
                                member.Name,
                                member.DeclaringType));

                        if (state.IsPublic)
                            throw new JazzConfigurationException(ConfigurationErrorType.StateMethodNotPrivate,
                                string.Format(
                                "The state method '{0}':'{1}' needs to be private.",
                                state.Name,
                                state.DeclaringType.FullName));
                    }
                    if ((stateAttrArray.Length > 0 && stateStartArray.Length > 0) ||
                        (stateStartArray.Length > 0 && finalStartArray.Length > 0) ||
                        (finalStartArray.Length > 0 && stateAttrArray.Length > 0))
                    {
                        List<string> alist = new List<string>();
                        if (stateAttrArray.Length > 0) alist.Add(typeof(StateAttribute).Name);
                        if (stateStartArray.Length > 0) alist.Add(typeof(StartStateAttribute).Name);
                        if (finalStartArray.Length > 0) alist.Add(typeof(FinalStateAttribute).Name);

                        throw new JazzConfigurationException(ConfigurationErrorType.DuplicateStateAttributesUsed,
                                string.Format(
                                "Only single 'State' attribute may be used.  The attributes '{0}' were used.  Only one of the attributes are allowed. " +
                                "  The attribute was used on a member '{1}':'{2}'",
                                string.Join("', '", alist.ToArray()),
                                member.Name,
                                member.DeclaringType));
                    }
                }
                #endregion
                #region Check ToState Attribute
                {

                    object[] stateToArray = member.GetCustomAttributes(typeof(StateToAttribute), true);
                    if (stateToArray.Length > 0)
                    {
                        if (states.FirstOrDefault(s => s.MethodInfo == member) == null)

                            throw new JazzConfigurationException(ConfigurationErrorType.StateToAttributeMustBeUsedOnState,
                                    string.Format(
                                    "The 'StateTo' attribute must be used on a state method.  " +
                                    "Add the attribute 'StartState', 'State' or 'FinalState' to make the method a state method." +
                                    "The attribute was used on a member '{0}':'{1}'",
                                    member.Name,
                                    member.DeclaringType));
                    }
                }
                #endregion
            }
            foreach (jState state in this.States)
            {
                #region State is void
                if (state.MethodInfo.ReturnType != typeof(void))
                    throw new JazzConfigurationException(ConfigurationErrorType.StateMethodNotVoid,
                        string.Format(
                        "The state method '{0}':'{1}' needs to have 'void' return type.",
                        state.Name,
                        state.MethodInfo.DeclaringType.FullName));
                #endregion
            }
        }


        #endregion
        #region InitializeJObjectFields
        int recursionDepth = 0;
        /// <summary>
        /// 
        /// </summary>
        internal void InitializeJObjectFields(jObject jazzObject)
        {
            foreach (FieldInfo f in this.GetFields())
            {
                if (f.GetValue(jazzObject) == null)
                {
                    // initialize only recognized objects
                    if (f.FieldType == typeof(String))
                    {
                        f.SetValue(jazzObject, "");
                        continue;
                    }
                    if (f.FieldType.IsSubclassOf(typeof(ClientNexusBase)))
                    {
                        f.SetValue(jazzObject, this.ClientNexus);
                        continue;
                    }
                    if (f.FieldType.IsSubclassOf(typeof(jObject)) )
                    {
                        if (f.IsDoNotAutoInitializeAttribute()) continue;
                        if (!this.ClientNexus.Cache.JClasses.Contains(f.FieldType))
                        {
                            if (f.Name.EndsWith("BackingField"))
                            {
                                string n = f.Name.Remove(0, 1);
                                string[] names = n.Split('>');
                                string propName = names[0];
                                PropertyInfo prop = f.ReflectedType.GetPrivatePublicProperty(propName);
                                if (prop.IsDoNotAutoInitializeAttribute()) continue;
                            }
                            bool boo = f.ReflectedType.IsDoNotAutoInitializeAttribute();
                            if (f.FieldType.IsAbstract)
                                // Can't do anything with abstract properties and fields that are not in 'JClasses' cache.
                                continue; 
                            if (recursionDepth > 3)
                                throw new ApplicationException(string.Format(
                                    "Could not initialize the field {0}:{1}.  The initialization routine appears to caught in a circular loop.",
                                    f.DeclaringType.FullName,f.Name ));
                            recursionDepth++;
                            try
                            {
                                this.ClientNexus.Bind(f.FieldType);
                            }
                            catch (Exception ex)
                            {
                                throw new ApplicationException(string.Format(
                                    "An error occurred when trying to create an empty object to auto initialize the field '{0}' - Class '{1}' " +
                                    "To prevent this error create the empty object separately or mark the field, or property, with the attribute 'DoNotAutoInitialize'.", 
                                    f.Name, f.ReflectedType.FullName), ex);
                            }
                            recursionDepth--;
                        }
                        object empty = this.ClientNexus.Cache.JClasses.Get(f.FieldType).EmptyInstance;
                        f.SetValue(jazzObject, empty);
                        continue;
                    }
                }
            }
        }
        #endregion
        #region GetFields
        private FieldInfo[] fieldsArray = null;
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        internal FieldInfo[] GetFields()
        {
            if (fieldsArray == null)
            {
                List<FieldInfo> list = new List<FieldInfo>();
                Type t = this.JazzObjectType;
                while (t.Name != "Object")
                {
                    FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                    list.AddRange(fields);
                    t = t.BaseType;
                }
                fieldsArray = list.ToArray();
            }
            return fieldsArray;
        }
        #endregion
        #region GetTag
        /// <summary>
        /// 
        /// </summary>
        /// <param name="member"></param>
        /// <param name="tagName"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        internal static object GetTag(MemberInfo member, string tagName, object defaultValue)
        {
            object tag = defaultValue;
            if (tagName != "")
            {
                FieldInfo tagField = null;
                PropertyInfo tagProperty = null;
                string[] sArray = tagName.Split('.');
                if (sArray.Length == 2)
                {
                    Assembly a = Assembly.GetAssembly(member.DeclaringType);
                    //Type[] tArray = a.GetTypes();
                    Type classType = a.GetTypes().FirstOrDefault(t => t.Name == sArray[0]);
                    //MemberInfo[] mArray = classType.GetMember(sArray[1]);
                    //MemberInfo m = mArray[0];
                    if (classType != null)
                        tagField = classType.GetField(sArray[1], BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy);
                    if (tagField == null)
                        tagProperty = classType.GetProperty(sArray[1], BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy);
                }
                else
                {
                    tagField = member.DeclaringType.GetField(tagName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy); 
                    if (tagField == null)
                        tagProperty = member.DeclaringType.GetProperty(tagName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy);
                }

                if (tagField != null)
                    tag = tagField.GetValue(null);
                else if (tagProperty != null)
                    tag = tagProperty.GetValue(null, new object[0]);
            }
            return tag;
        }
        #endregion
        #region GetField
        /// <summary>
        /// 
        /// </summary>
        /// <param name="t"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        internal static FieldInfo GetField(Type t, string name)
        {
            FieldInfo field;
            while (t.Name != "Object")
            {
                field = t.GetField(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                if (field != null)
                {
                    return field;
                }
                t = t.BaseType;
            }
            throw new ArgumentException("Field not found");
        }
        #endregion
        #region GetMethodInfo
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        internal MethodInfo GetMethodInfo(string name)
        {
            MethodInfo method;
            if(!TryGetMethodInfo(name, out method))
                throw new ArgumentException(string.Format("Field {0} was not found", name));
            return method;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        internal bool TryGetMethodInfo(string name, out MethodInfo method)
        {
            Type t = this.JazzObjectType;
            do
            {
                method = t.GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                if (method != null)
                {
                    return true;
                }
                t = t.BaseType;
            }
            while (t.Name != "Object");
            return false;
        }
        #endregion
        #region GetJazzMemberInfo
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        internal jMemberInfo GetJazzMemberInfo(string name)
        {
            return GetJazzMemberInfo(name, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="jObj"></param>
        /// <returns></returns>
        internal jMemberInfo GetJazzMemberInfo(string name, jObject jObj)
        {
            Type t = this.JazzObjectType;
            while (t.Name != "jObject")
            {
                MethodInfo method = t.GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                if (method != null)
                {
                    return new jMethodInfo(method, jObj);
                }
                PropertyInfo property = t.GetProperty(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                if (property != null)
                {
                    return new jPropertyInfo(property, jObj);
                }
                t = t.BaseType;
            }
            return jMethodInfo.EmptyMethod;
        }
        #endregion
        #region JazzObjectType
        /// <summary>
        /// 
        /// </summary>
        public Type JazzObjectType
        {
            get;
            private set;
        }
        #endregion
        #region IsJazzObjectNameUnique
        /// <summary>
        /// 
        /// </summary>
        public bool IsJazzObjectNameUnique
        {
            get;
            private set;
        }
        #endregion
        #region EmptyInstance
        /// <summary>
        /// 
        /// </summary>
        public jObject EmptyInstance
        {
            get;
            internal set;
        }
        #endregion
        #region Rules
        private RulesCollection rules;
        /// <summary>
        /// Returns a collection containing the 'Rules' for this 'JazzObjectType'.
        /// </summary>
        public RulesCollection Rules
        {
            get
            {
                if (this.rules == null)
                    this.rules = RulesCollection.Create(this);
                return rules;
            }
        }
        #endregion
        #region States
        private StatesCollection states;
        /// <summary>
        /// Returns a collection containing the 'States' for this 'JazzObjectType'.
        /// </summary>
        public StatesCollection States
        {
            get
            {
                if (this.states == null)
                    this.states = StatesCollection.Create(this.JazzObjectType);
                return states;
            }
        }
        #endregion
        #region StateTable
        private StateTable stateTable;
        /// <summary>
        /// Returns a 'StateTable' for this 'JazzObjectType'.
        /// </summary>
        public StateTable StateTable
        {
            get
            {
                if (this.stateTable == null)
                    this.stateTable = StateTable.Create(this);
                return stateTable;
            }
        }
        #endregion
        #region ID
        /// <summary>
        /// Gets the Guid value which is used to store and retrieve objects from the nexus.
        /// </summary>
        public Guid ID
        {
            get
            {
                return this.guid;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        protected internal Guid guid;
        #endregion
        #region GetMembers
        /// <summary>
        /// Convenience method that gets the members of this workflow.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<MemberInfo> GetMembers()
        {
            return this.JazzObjectType.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
        }
        #endregion
        #region Status
        /// <summary>
        /// Objects can be Valid, Broken, Deleted, Disconnected or Superceeded.
        /// </summary>

        protected jLifeCycleType statusType = jLifeCycleType.Valid;
        /// <summary>
        /// The status of Nexus objects.  
        /// Objects can be Valid, Deleted, Removed, etc.
        /// </summary>
        public jLifeCycleType Status
        {
            get { return statusType; }
        }
        #endregion
        #region StorageStatus
        /// <summary>
        /// Changing the name requires that XConst and that 
        /// all ITjNexusStorageUnit be changed as well.
        /// </summary>
        protected jStorageStatusType storageStatusType = jStorageStatusType.Pending;
        /// <summary>
        /// Indicates whether an object's data is pending or confirmed.
        /// 'Pending' would indicate that an object has been changed but the server
        /// nexus has not yet 'Confirmed' the operation.
        /// </summary>
        public jStorageStatusType StorageStatus
        {
            get { return this.storageStatusType; }
        }
        #endregion
        #region TimeStamp
        /// <summary>
        /// Changing the name requires that XConst and that 
        /// all ITjNexusStorageUnit be changed as well.
        /// </summary>
        private jTimestamp timeStamp = jTimestamp.Empty;
        /// <summary>
        /// Returns the timeStamp that was issued by server nexus.
        /// When blank the object has been created but not written for the first time.
        /// The time stamp is only valid if '<see cref="StorageStatus"/>' is confirmed.
        /// Time stamps are unique for each write operation that occurs in a single server nexus.
        /// </summary>
        public jTimestamp Timestamp
        {
            get { return this.timeStamp; }
        }

        #endregion
        #region Nexus
        /// <summary>
        /// Returns the nexus object.
        /// </summary>
        public ClientNexusBase ClientNexus
        {
            get;
            internal set;
        }
        #endregion
        #region Workspace
        /// <summary>
        /// Do not change name.  See CN.WORKSPACE.
        /// </summary>
        protected jObject workspace;
        /// <summary>
        /// Returns the 'Workspace' that the <see cref="jClass"/> resides in.
        /// A <see cref="jClass"/> must reside in 1 workspace and only 1 workspace.
        /// </summary>
        public jObject Workspace
        {
            get { return this.workspace; }
            protected set { this.workspace = value; }
        }

        
        #endregion
        #region IsRoot

        /// <summary>
        /// 
        /// </summary>
        protected bool isRoot = false;
        /// <summary>
        /// Returns true if this object is a 'root' object.
        /// <para>A Root object, even if it has no references, is not removed by the GC unless it is deleted.</para>
        /// </summary>
        public bool IsRoot
        {
            get { return this.isRoot; }
        }
        #endregion

        #region ITjNexusStorageUnit Members
        
        /// <summary>
        /// 
        /// </summary>
        public jStorageType StorageType
        {
            get { return jStorageType.Class; }
        }
        /// <summary>
        /// 
        /// </summary>
        public string Name
        {
            get { return this.JazzObjectType.Name; }
        }
        /// <summary>
        /// 
        /// </summary>
        public string FullName
        {
            get { return this.JazzObjectType.FullName; }
        }


        #endregion

        #region INotifyPropertyChanged Members
        /// <summary>
        /// Not implemented.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        private void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, e);
        }

        #endregion

        #region ToString
        /// <summary>
        /// Designed to be useful for Visual Studio.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            string name;
            if (this.Name == "")
            {
                    name = this.ID.ToString();
            }
            else
                name = this.Name;
            return this.GetType().Name + ":'" + name + "'";
        }
        #endregion
    }
}
